#ifndef CAMERA_H
#define CAMERA_H

#include <QMatrix>
#include <QtOpenGL>
#include <QQuaternion>
#include <iostream>
#include <cmath>

using namespace std;

inline static QMatrix4x4 getBiasMatrix(){
    QMatrix4x4 self;
    self.scale(0.5);
    self.translate(1.0, 1.0, 1.0);
    return self;
}

inline static void println(QMatrix4x4 m, string name = "matrix"){
    double *p = m.data();
    std::cout << name << ": " << std::endl;
    std::cout << p[0] << "\t" << p[1] << "\t" << p[2] << "\t" << p[3] << std::endl;
    std::cout << p[4] << "\t" << p[5] << "\t" << p[6] << "\t" << p[7] << std::endl;
    std::cout << p[8] << "\t" << p[9] << "\t" << p[10] << "\t" << p[11] << std::endl;
    std::cout << p[12] << "\t" << p[13] << "\t" << p[14] << "\t" << p[15] << std::endl;
    std::cout << std::endl;
}

inline static void println(QVector3D v, string name = "vector"){
    std::cout << name << ": " << std::endl;
    std::cout << v.x() << "\t" << v.y() << "\t" << v.z() << std::endl;
    std::cout << std::endl;
}

class Camera
{
public:
    Camera(){
        eye = QVector3D(0.0, 0.0, 0.0);
        target = QVector3D(0.0, 0.0, -1.0);
        up = QVector3D(0.0, 1.0, 0.0);
        normal = QVector3D::crossProduct(up, target);

        setThetaPhi();

        perspective.perspective(70, 1.0, 1.0, 100.0);
        projection.lookAt(eye, target, up);
    }

    void lookAt(QVector3D _eye, QVector3D _target, QVector3D _up){
        eye = _eye;
        target = _target - _eye;
        target.normalize();
        up = _up;
        up.normalize();
        normal = QVector3D::crossProduct(up, target);

        setThetaPhi();

        projection.setToIdentity();
        projection.lookAt(eye, eye+ target, up);
    }

    void setPerspective(float _fovy, float _aspect, float _zNear, float _zFar){
        fovy = _fovy;
        aspect = _aspect;
        zNear = _zNear;
        zFar = _zFar;

        perspective.setToIdentity();
        perspective.perspective(fovy, aspect, zNear, zFar);
    }

    void turnYourself(float _theta, float _phi){
        phi += -_phi*M_PI/180.0;
        theta += -_theta*M_PI/180.0;

        if (phi < M_PI/12)
            phi = 0.0;
        else if (phi > M_PI - M_PI/12)
            phi = M_PI - M_PI/12;

        target.setX(sin(phi)*cos(theta));
        target.setZ(sin(phi)*sin(theta));
        target.setY(cos(phi));
        normal = QVector3D::crossProduct(up, target);

        projection.setToIdentity();
        projection.lookAt(eye, eye + target, up);
    }

    void rotate(float x, float y, float z, float angle){
        QMatrix4x4 rot;
        rot.rotate(QQuaternion::fromAxisAndAngle(x, y, z, angle));
        eye = rot * eye;
        target = rot * target;
        projection.rotate(QQuaternion::fromAxisAndAngle(x, y, z, -angle));
    }

    void translate(float x, float y, float z){
        eye += x*target;
        eye += y*normal;
        eye += z*up;
        projection.setToIdentity();
        projection.lookAt(eye, eye + target, up);
    }

    QMatrix4x4 getProjection(){ return projection; }
    QMatrix4x4 getPerspective(){ return perspective; }

    QVector3D getEye(){ return eye; }
    QVector3D getTarget(){ return target; }
    QVector3D getUp(){ return up; }

    void setEye(const QVector3D & new_eye)
    {
        eye.setX(new_eye.x());
        eye.setZ(new_eye.y());
        eye.setZ(new_eye.z());
    }
    void setTarget(const QVector3D & new_target)
    {
        target.setX(new_target.x());
        target.setY(new_target.y());
        target.setZ(new_target.z());
    }
    void setUp(const QVector3D & new_up)
    {
        up.setX(new_up.x());
        up.setY(new_up.y());
        up.setZ(new_up.z());
    }

    double getFovy(){ return fovy; }
    double getAspect(){ return aspect; }
    double getzNear(){ return zNear; }
    double getzFar(){ return zFar; }

protected:
    void setThetaPhi(){
        if(target.z() >= 0.0)
            theta = acos(target.x());
        else
            theta = 2 * M_PI - acos(target.x());
        phi = acos(target.y());
    }

protected:
    QVector3D eye, target, up, normal;
    double fovy, aspect, zNear, zFar;

private:
    QMatrix4x4 perspective, projection;

    double theta, phi;
};

#endif // CAMERA_H
